using System;
using System.IO;
using NFreeMarker.Core;
using NUnit.Framework;

// ReSharper disable InconsistentNaming
namespace NFreeMarker.Tests.Template
{
    public class TemplateTests_ProcessDirectives
    {
        [TestFixture]
        public class When_processing_an_assign_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_a_new_variable_can_be_declared()
            {
                SetTemplateContent(
                    "<#assign message='Hello world'>" +
                    "${message}");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello world"));
            }

            [Test]
            public void Assure_an_existing_variable_can_be_replaced()
            {
                SetTemplateContent(
                    "<#assign value=value + 1>" +
                    "${value}");
                AddTemplateVariable("value", 1);

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("2"));
            }
        }

        [TestFixture]
        public class When_processing_an_attempt_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_the_attempt_block_is_outputted_when_no_error_occurs()
            {
                SetTemplateContent(
                    "<#attempt>" +
                    "Hello ${name}" +
                    "<#recover>" +
                    "An error occured" +
                    "</#attempt>");
                AddTemplateVariable("name", "Bob");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello Bob"));
            }

            [Test]
            public void Assure_the_recover_block_is_outputted_when_an_error_occurs()
            {
                SetTemplateContent(
                    "<#attempt>" +
                    "Hello ${name}" +
                    "<#recover>" +
                    "An error occured" +
                    "</#attempt>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("An error occured"));
            }
        }

        [TestFixture]
        public class When_processing_a_compress_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_the_first_whitespace_sequence_is_removed()
            {
                SetTemplateContent("<#compress>   Hello World!</#compress>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello World!"));
            }

            [Test]
            public void Assure_the_last_whitespace_sequence_is_removed()
            {
                SetTemplateContent("<#compress>Hello World!   </#compress>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello World!"));
            }

            [Test]
            public void Assure_whitespace_sequence_is_replace_with_single_whitespace()
            {
                SetTemplateContent("<#compress>Hello    World!</#compress>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello World!"));
            }

            [Test]
            public void Assure_whitespace_sequence_with_newline_is_replace_with_single_newline()
            {
                SetTemplateContent("<#compress>Hello  \n  World!</#compress>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello\nWorld!"));
            }
        }

        [TestFixture]
        public class When_processing_an_if_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_section_is_inserted_when_expression_is_true()
            {
                SetTemplateContent("<#if true>yes</#if>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("yes"));
            }

            [Test]
            public void Assure_section_is_not_inserted_when_expression_is_false()
            {
                SetTemplateContent("<#if false>yes</#if>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo(""));
            }
        }

        [TestFixture]
        public class When_processing_an_if_else_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_first_section_is_inserted_when_if_expression_is_true()
            {
                SetTemplateContent("<#if true>One<#else>Two</#if>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("One"));
            }

            [Test]
            public void Assure_second_section_is_inserted_when_if_expression_is_false()
            {
                SetTemplateContent("<#if false>One<#else>Two</#if>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Two"));
            }
        }

        [TestFixture]
        public class When_processing_an_if_elseif_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_first_section_is_inserted_when_if_expression_is_true()
            {
                SetTemplateContent("<#if true>One<#elseif true>Two</#if>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("One"));
            }

            [Test]
            public void Assure_second_section_is_inserted_when_if_expression_is_false_and_else_if_expression_is_true()
            {
                SetTemplateContent("<#if false>One<#elseif true>Two</#if>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Two"));
            }

            [Test]
            public void Assure_nothing_is_inserted_when_both_expressions_are_false()
            {
                SetTemplateContent("<#if false>One<#elseif false>Two</#if>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo(""));
            }
        }

        [TestFixture]
        public class When_processing_an_if_elseif_else_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_first_section_is_inserted_when_if_expression_is_true()
            {
                SetTemplateContent("<#if true>One<#elseif true>Two<#else>Three</#if>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("One"));
            }

            [Test]
            public void Assure_second_section_is_inserted_when_if_expression_is_false_and_else_if_expression_is_true()
            {
                SetTemplateContent("<#if false>One<#elseif true>Two<#else>Three</#if>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Two"));
            }

            [Test]
            public void Assure_third_section_is_inserted_when_both_expressions_are_false()
            {
                SetTemplateContent("<#if false>One<#elseif false>Two<#else>Three</#if>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Three"));
            }
        }

        [TestFixture]
        public class When_processing_an_include_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_the_content_is_parsed_by_default()
            {
                SetTemplateContent("testInclude.ftl", "Hello ${name}!");
                SetTemplateContent("<#include 'testInclude.ftl'>");
                AddTemplateVariable("name", "Bob");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello Bob!"));
            }

            [Test]
            public void Assure_the_content_is_not_parsed_when_parse_attribute_is_false()
            {
                SetTemplateContent("testInclude.ftl", "Hello ${name}!");
                SetTemplateContent("<#include 'testInclude.ftl' parse=false>");
                AddTemplateVariable("name", "Bob");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello ${name}!"));
            }

            [Test]
            public void Assure_the_parse_attribute_can_be_a_string()
            {
                SetTemplateContent("testInclude.ftl", "Hello ${name}!");
                SetTemplateContent("<#include 'testInclude.ftl' parse='false'>");
                AddTemplateVariable("name", "Bob");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello ${name}!"));
            }

            [Test]
            public void Assure_the_parse_attribute_can_be_an_expression()
            {
                SetTemplateContent("testInclude.ftl", "Hello ${name}!");
                SetTemplateContent("<#include 'testInclude.ftl' parse=shallParse>");
                AddTemplateVariable("name", "Bob");
                AddTemplateVariable("shallParse", "false");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello ${name}!"));
            }

            [Test]
            public void Assure_a_ParseException_is_thrown_when_the_parse_attribute_is_a_number()
            {
                SetTemplateContent("testInclude.ftl", "Hello ${name}!");
                SetTemplateContent("<#include 'testInclude.ftl' parse=2>");
                AddTemplateVariable("name", "Bob");

                Assert.That(
                    () => ProcessTemplate(),
                    Throws.Exception.TypeOf<ParseException>().
                        With.Message.ContainsSubstring(
                            "Expected a boolean or string as the value of the parse attribute"));
            }

            [Test]
            public void Assure_an_encoding_attribute_can_be_specified()
            {
                SetTemplateContent("testInclude.ftl", "Hello ${name}!");
                SetTemplateContent("<#include 'testInclude.ftl' encoding='utf-8'>");
                AddTemplateVariable("name", "Bob");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello Bob!"));
            }
        }

        [TestFixture]
        public class When_processing_a_macro_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_a_parameterless_macro_can_be_called()
            {
                SetTemplateContent(
                    "<#macro hello>" +
                    "Hello world" +
                    "</#macro>" +
                    "<@hello/>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello world"));
            }

            [Test]
            public void Assure_a_macro_with_a_parameter_can_be_called()
            {
                SetTemplateContent(
                    "<#macro hello name>" +
                    "Hello ${name}!" +
                    "</#macro>" +
                    "<@hello name='Bob'/>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello Bob!"));
            }

            [Test]
            public void Assure_a_macro_with_a_default_parameter_can_be_called_with_a_new_value()
            {
                SetTemplateContent(
                    "<#macro hello name='John'>" +
                    "Hello ${name}!" +
                    "</#macro>" +
                    "<@hello name='Bob'/>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello Bob!"));
            }

            [Test]
            public void Assure_a_macro_with_a_default_parameter_can_be_called_without_a_new_value()
            {
                SetTemplateContent(
                    "<#macro hello name='John'>" +
                    "Hello ${name}!" +
                    "</#macro>" +
                    "<@hello/>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello John!"));
            }

            [Test]
            [Ignore("Throws exception. Possible bug?")]
            public void Assure_a_macro_with_params_support_can_be_called_without_parameters()
            {
                SetTemplateContent(
                    "<#macro dict args...>" +
                    "<#list args?keys as name>" +
                    "[${name}=${args[name]}]" +
                    "</#list>" +
                    "</#macro>" +
                    "<@dict/>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo(""));
            }

            [Test]
            public void Assure_a_macro_with_params_support_can_be_called_with_one_parameter()
            {
                SetTemplateContent(
                    "<#macro dict args...>" +
                    "<#list args?keys as name>" +
                    "[${name}=${args[name]}]" +
                    "</#list>" +
                    "</#macro>" +
                    "<@dict name='Bob'/>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("[name=Bob]"));
            }

            [Test]
            public void Assure_a_macro_with_params_support_can_be_called_with_two_parameters()
            {
                SetTemplateContent(
                    "<#macro dict args...>" +
                    "<#list args?keys as name>" +
                    "[${name}=${args[name]}]" +
                    "</#list>" +
                    "</#macro>" +
                    "<@dict name='Bob' age='42'/>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("[name=Bob][age=42]"));
            }
        }

        [TestFixture]
        public class When_processing_a_macro_nested_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_a_parameterless_nested_directive_can_be_called()
            {
                SetTemplateContent(
                    "<#macro hello>" +
                    "Message: <#nested>" +
                    "</#macro>" +
                    "<@hello>Hello world</@hello>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Message: Hello world"));
            }

            [Test]
            public void Assure_a_nested_directive_can_be_closed_with_nameless_end_tag()
            {
                SetTemplateContent(
                    "<#macro hello>" +
                    "Message: <#nested>" +
                    "</#macro>" +
                    "<@hello>Hello world</@>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Message: Hello world"));
            }

            [Test]
            public void Assure_a_nested_directive_with_a_parameter_can_be_called()
            {
                SetTemplateContent(
                    "<#macro hello>" +
                    "<#nested 'world'>" +
                    "</#macro>" +
                    "<@hello; name>Hello ${name}</@hello>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello world"));
            }

            [Test]
            public void Assure_a_nested_directive_with_two_parameters_can_be_called()
            {
                SetTemplateContent(
                    "<#macro hello>" +
                    "<#nested 'world', 'How are you?'>" +
                    "</#macro>" +
                    "<@hello; name, message>Hello ${name}. ${message}</@hello>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello world. How are you?"));
            }
        }

        [TestFixture]
        public class When_processing_a_macro_return_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_the_text_after_the_return_directive_is_ignored()
            {
                SetTemplateContent(
                    "<#macro hello>" +
                    "Hello world" +
                    "<#return>" +
                    "This text shall be ignored." +
                    "</#macro>" +
                    "<@hello/>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello world"));
            }
        }

        [TestFixture]
        public class When_processing_a_noparse_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_the_item_value_is_outputted()
            {
                SetTemplateContent("<#noparse>Hello ${name}!</#noparse>");
                AddTemplateVariable("animal", "Bob");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello ${name}!"));
            }
        }

        [TestFixture]
        public class When_processing_a_list_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_the_item_value_is_outputted()
            {
                SetTemplateContent(
                    "<#list seq as x>" +
                    "${x}" +
                    "</#list>");
                AddTemplateVariable("seq", new [] { "Hello", "World" });

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("HelloWorld"));
            }

            [Test]
            public void Assure_the_item_index_is_outputted()
            {
                SetTemplateContent(
                    "<#list seq as x>" +
                    "${x_index}" +
                    "</#list>");
                AddTemplateVariable("seq", new [] { "Hello", "World" });

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("01"));
            }

            [Test]
            public void Assure_the_has_next_value_is_set_correctly()
            {
                SetTemplateContent(
                    "<#list seq as x>" +
                    "${x_has_next?string}" +
                    "</#list>");
                AddTemplateVariable("seq", new [] { "Hello", "World" });

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("truefalse"));
            }

            [Test]
            public void Assure_the_item_and_index_and_has_next_works_simultaneously()
            {
                SetTemplateContent(
                    "<#list seq as x>" +
                    "${x_index}: ${x}<#if x_has_next>, </#if>" +
                    "</#list>");
                AddTemplateVariable("seq", new [] { "Hello", "World" });

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("0: Hello, 1: World"));
            }

            [Test]
            public void Assure_break_leaves_the_loop()
            {
                SetTemplateContent(
                    "<#list seq as x>" +
                    "${x}<#break>" +
                    "</#list>");
                AddTemplateVariable("seq", new [] { "Hello", "World" });

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello"));
            }
        }

        [TestFixture]
        public class When_processing_a_stop_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_a_StopException_is_thrown()
            {
                SetTemplateContent("<#stop>");

                Assert.That(
                    () => ProcessTemplate(),
                    Throws.Exception.TypeOf<StopException>());
            }

            [Test]
            public void Assure_a_StopException_is_thrown_with_the_specified_message()
            {
                SetTemplateContent("<#stop 'My error message'>");

                Assert.That(
                    () => ProcessTemplate(),
                    Throws.Exception.TypeOf<StopException>().
                    With.Message.ContainsSubstring("My error message"));
            }
        }

        [TestFixture]
        public class When_processing_a_switch_case_default_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_first_section_is_inserted_when_the_first_case_is_a_match()
            {
                SetTemplateContent(
                    "<#switch '1'>" + 
                    "  <#case '1'>One<#break>" + 
                    "  <#case '2'>Two<#break>" +
                    "  <#default>Three" +
                    "</#switch>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("One"));
            }

            [Test]
            public void Assure_second_section_is_inserted_when_the_second_case_is_a_match()
            {
                SetTemplateContent(
                    "<#switch '2'>" + 
                    "  <#case '1'>One<#break>" + 
                    "  <#case '2'>Two<#break>" +
                    "  <#default>Three" +
                    "</#switch>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Two"));
            }

            [Test]
            public void Assure_default_section_is_inserted_when_neither_case_is_a_match()
            {
                SetTemplateContent(
                    "<#switch '3'>" +
                    "  <#case '1'>One<#break>" +
                    "  <#case '2'>Two<#break>" +
                    "  <#default>Three" +
                    "</#switch>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Three"));
            }

            [Test]
            public void Assure_nothing_is_inserted_when_neither_case_is_a_match_and_no_default_exists()
            {
                SetTemplateContent(
                    "<#switch '3'>" +
                    "  <#case '1'>One<#break>" +
                    "  <#case '2'>Two<#break>" +
                    "</#switch>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo(""));
            }
        }

        [TestFixture]
        public class When_processing_a_switch_case_with_no_break_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_all_three_sections_are_inserted_when_the_first_case_is_a_match()
            {
                SetTemplateContent(
                    "<#switch '1'>" + 
                    "  <#case '1'>One" + 
                    "  <#case '2'>Two" +
                    "  <#default>Three" + 
                    "</#switch>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("One  Two  Three"));
            }

            [Test]
            public void Assure_second_and_default_section_are_inserted_when_the_second_case_is_a_match()
            {
                SetTemplateContent(
                    "<#switch '2'>" + 
                    "  <#case '1'>One" + 
                    "  <#case '2'>Two" +
                    "  <#default>Three" +
                    "</#switch>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Two  Three"));
            }

            [Test]
            public void Assure_default_section_is_inserted_when_neither_case_is_a_match()
            {
                SetTemplateContent(
                    "<#switch '3'>" +
                    "  <#case '1'>One" +
                    "  <#case '2'>Two" +
                    "  <#default>Three" +
                    "</#switch>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Three"));
            }
        }

        [TestFixture]
        public class When_processing_a_trim_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_the_leading_ws_are_removed()
            {
                SetTemplateContent("  Hello<#t>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello"));
            }

            [Test]
            public void Assure_the_leading_ws_after_the_tag_are_not_removed()
            {
                SetTemplateContent(" <#t>  Hello");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("  Hello"));
            }

            public void Assure_the_trailing_ws_are_removed()
            {
                SetTemplateContent("<#t>Hello  ");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello  "));
            }

            [Test]
            [Ignore("Doesn't remove the white-space. Bug?")]
            public void Assure_the_trailing_ws_before_the_tag_are_not_removed()
            {
                SetTemplateContent("Hello  <#t> ");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello  "));
            }

            [Test]
            public void Assure_the_trailing_ws_and_newline_are_removed()
            {
                SetTemplateContent("Hello  <#t> \n");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello  "));
            }
        }

        [TestFixture]
        public class When_processing_a_trim_left_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_the_leading_ws_are_removed()
            {
                SetTemplateContent("  Hello<#lt>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello"));
            }

            [Test]
            public void Assure_the_leading_ws_after_the_tag_are_not_removed()
            {
                SetTemplateContent(" <#lt>  Hello");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("  Hello"));
            }

            [Test]
            public void Assure_the_trailing_ws_are_not_removed()
            {
                SetTemplateContent("<#lt>Hello  ");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello  "));
            }

            [Test]
            public void Assure_the_trailing_newline_is_not_removed()
            {
                SetTemplateContent("Hello  <#lt> \n");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello   \n"));
            }
        }

        [TestFixture]
        public class When_processing_a_trim_right_directive : TemplateTestsBase
        {
            [Test]
            public void Assure_the_leading_ws_are_not_removed()
            {
                SetTemplateContent("  Hello<#rt>");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("  Hello"));
            }

            [Test]
            [Ignore("Doesn't remove the whitespaces. Bug?")]
            public void Assure_the_trailing_ws_are_removed()
            {
                SetTemplateContent("<#rt>Hello  ");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello"));
            }

            [Test]
            [Ignore("Doesn't remove the whitespaces. Bug?")]
            public void Assure_the_trailing_ws_before_the_tag_are_not_removed()
            {
                SetTemplateContent("Hello  <#rt> ");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello  "));
            }

            [Test]
            public void Assure_the_trailing_ws_and_the_newline_are_removed()
            {
                SetTemplateContent("Hello  <#rt> \n");

                string output = ProcessTemplate();

                Assert.That(output, Is.EqualTo("Hello  "));
            }
        }
    }
}
// ReSharper restore InconsistentNaming
